import {
  DVault,
  genUUID,
  NoteUtils,
  SchemaOpts,
  SchemaUtils,
} from "@dendronhq/common-all";
import { NoteTestUtilsV4 } from "@dendronhq/common-test-utils";
import { runEngineTestV5 } from "../../engine";
import { makeSchemaTests, SchemaTest } from "../../utils/schema";

describe(`NoteUtils tests:`, () => {
  describe(`genSchemaDesc tests`, () => {
    const vault = { fsPath: "/tmp/ws/vault1" };
    const SCHEMA_ID = "id-1";

    async function testGenSchemaDesc(
      schemaCreateOpts: SchemaOpts & { vault: DVault },
      expectedDescription: string
    ) {
      const schema = SchemaUtils.createFromSchemaOpts(schemaCreateOpts);

      const wsRoot = "/tmp/ws/";
      const schemaModuleProps = await NoteTestUtilsV4.createSchema({
        fname: "/tmp/fname1",
        vault,
        wsRoot,
        noWrite: true,
      });
      schemaModuleProps.schemas[SCHEMA_ID] = schema;

      const note = await NoteTestUtilsV4.createNote({
        vault,
        wsRoot,
        noWrite: true,
        fname: "f1",
        props: {
          schema: {
            schemaId: schema.id,
            moduleId: "irrelevant",
          },
        },
      });

      const desc = NoteUtils.genSchemaDesc(note, schemaModuleProps);
      expect(desc).toEqual(expectedDescription);
    }

    it(`WHEN id is auto generated THEN use the pattern.`, async () => {
      await testGenSchemaDesc(
        {
          fname: "hi",
          id: SCHEMA_ID,
          data: { pattern: "pattern-val", isIdAutoGenerated: true },
          vault,
        },
        "F1 $(repo) /tmp/fname1 $(breadcrumb-separator) pattern-val"
      );
    });

    it(`WHEN id is auto generated AND title is different than id then use title`, async () => {
      await testGenSchemaDesc(
        {
          fname: "hi",
          title: "title-val",
          id: SCHEMA_ID,
          data: { pattern: "pattern-val", isIdAutoGenerated: true },
          vault,
        },
        "F1 $(repo) /tmp/fname1 $(breadcrumb-separator) title-val"
      );
    });

    it(`WHEN id is not auto generated AND title is equal to id THEN use title.`, async () => {
      await testGenSchemaDesc(
        {
          fname: "hi",
          title: SCHEMA_ID,
          id: SCHEMA_ID,
          data: { pattern: "pattern-val" },
          vault,
        },
        `F1 $(repo) /tmp/fname1 $(breadcrumb-separator) ${SCHEMA_ID}`
      );
    });

    it(`WHEN id is not auto generated AND title is omitted THEN use id.`, async () => {
      await testGenSchemaDesc(
        {
          fname: "hi",
          title: undefined,
          id: SCHEMA_ID,
          data: { pattern: "pattern-val" },
          vault,
        },
        `F1 $(repo) /tmp/fname1 $(breadcrumb-separator) ${SCHEMA_ID}`
      );
    });
  });

  describe("WHEN generating note titles with the full hierarchy format", () => {
    test("THEN title is formatted correctly for single level hierarchy note", () => {
      expect(NoteUtils.genTitleFromFullFname("foo")).toEqual("Foo");
    });

    test("THEN title is formatted correctly for multi-level hierarchy note", () => {
      expect(NoteUtils.genTitleFromFullFname("foo.bar.test")).toEqual(
        "Foo Bar Test"
      );
    });
  });
});

describe(`SchemaUtil tests:`, () => {
  describe("Schema match tests", () => {
    describe("GIVEN schema anatomy example from the wiki", () => {
      runAllTests(
        makeSchemaTests({
          schema: `version: 1
schemas:
# this will match "cli.*" notes
- id: cli 
  # human readable description of hierarchy
  desc: command line interface reference
  # add this to the domain of your schema hierarchy
  parent: root
  # when a schema is a namespace, it can have arbitrary children. equivalent to cli.* glob pattern
  namespace: true 
  children:
    - cmd
    - env
# will match cli.*.env
- id: env
  desc: cli specific env variables
# will match cli.*.cmd.*
- id: cmd
  desc: subcommands 
  namespace: true`,
          checks: {
            "cli.fd": "cli",
            "cli.fd.bar.usage": false,
            "cli.fd.cmd.usage": "cmd",
            "cli.fd.env": "env",
            "foo.bar": false,
          },
          expect,
        })
      );
    });

    describe("GIVEN inline schema anatomy example from the wiki", () => {
      runAllTests(
        makeSchemaTests({
          schema: `version: 1
schemas:
# Daily is the top most schema since its parent is 'root' it must have an identifier
# this identifier 'daily' will be used when using 'Lookup (schema)' command.
- id: daily
  parent: root
  # Children of the top most schema do not need to contain identifier and just 
  # require a 'pattern' to be set to match the hierarchy of notes.
  children:
    - pattern: journal
      children:
        - pattern: "[0-2][0-9][0-9][0-9]"
          children:
            - pattern: "[0-1][0-9]"
              children:
                - pattern: "[0-3][0-9]"
                  # As with regular schema we can set the template to be used with
                  # the match of our notes. Below is an example usage of shorthand template
                  # definition (which defaults to type: note). 
                  template: templates.daily`,
          checks: {
            "daily.journal.2021.10.24": true,
            "journal.2021.10.24": false,
            "daily.journal.5000.10.24": false,
            "daily.journal.2021.10.foo": false,
            "daily.journal.2021.10.24.foo": false,
          },
          expect,
        })
      );
    });

    describe("GIVEN daily journal pattern from the wiki", () => {
      runAllTests(
        makeSchemaTests({
          schema: `version: 1
schemas:
- id: journal
  title: journal
  desc: ""
  parent: root
  children:
    - year
- id: year
  title: year
  pattern: "[0-2][0-9][0-9][0-9]"
  children: 
    - month
- id: month
  title: month
  pattern: "[0-9][0-9]"
  children: 
    - day
- id: day
  title: day
  pattern: "[0-9][0-9]"
  namespace: true`,
          checks: {
            journal: "journal",
            "journal.2020": "year",
            "journal.2020.09": "month",
            "journal.2020.09.12": "day",
            "journal.2020.09.12.foo": true,
            "daily.journal.2021.10.24": false,
            "journal.5000.10.24": false,
            "journal.2021.10.foo": false,
          },
          expect,
        })
      );
    });

    describe("GIVEN negated pattern", () => {
      runAllTests(
        makeSchemaTests({
          schema: `version: 1
schemas:
- id: projects
  parent: root
  children:
    - name
- id: name
  pattern: "!(scratch)"`,
          checks: {
            "projects.web-app": "name",
            "web-app": false,
            "projects.scratch": false,
          },
          expect,
        })
      );
    });

    describe("GIVEN dendron-docs RFC schema", () => {
      runAllTests(
        makeSchemaTests({
          schema: `version: 1
schemas:
  - id: rfc
    title: RFC
    parent: root
    children:
      - pattern: "+([0-9])-+([!])" # starts with 1 or more digits, a dash, then one or more characters
        template: dendron://dendron.docs/templates.rfc`,
          checks: {
            "rfc.41-test": true,
            "rfc.41": false,
            "rfc.test": false,
            "rfc.41-": false,
            "rfc.-test": false,
            "rfc.123123-foo-bar-baz": true,
          },
          expect,
        })
      );
    });

    describe("GIVEN a schema with namespace node", () => {
      runAllTests(
        makeSchemaTests({
          schema: `version: 1
imports: []
schemas:
  - id: test
    children: 
      - testing
    title: test
    parent: root
  - id: testing
    pattern: "*"
    title: "testing"
    namespace: true
    template:
      id: template.test
      type: note`,
          checks: {
            test: true,
            "test.test1": true,
            "test.foo": true,
            "test.test1.test2": true,
            "test.test1.foo": true,
            "test.test1.test2.test3": false,
            "test.foo.bar.baz": false,
          },
          expect,
        })
      );
    });
  });
});

/** Run all schema tests.
 *
 * Supports `.skip` pattern (you can write `runAllTests.skip(...` if you are trying to skip a test).
 *
 * NOTE: space between o nly because of husky hook that will block test otherwise
 * Also supports `.o nly` pattern, but you need to specify which test to do.
 * Should look like `runAllTests.o nly("rfc.41", ...`
 */
function runAllTests(tests: SchemaTest[], onlyFname?: string) {
  tests.forEach(({ testCase, preSetupHook, name, testedFname }) => {
    const testfn = onlyFname && onlyFname !== testedFname ? test.only : test;
    testfn(name, async () => {
      await runEngineTestV5(testCase, {
        preSetupHook,
        expect,
      });
    });
  });
}
runAllTests.only = (onlyFname: string, tests: SchemaTest[]) =>
  runAllTests(tests, onlyFname);
runAllTests.skip = (tests: SchemaTest[]) =>
  runAllTests(
    tests,
    genUUID() /* No test will match, so everything will get skipped */
  );
